//
// Created by lau on 2019-10-17.
//

#include <vector>
#include <GLES2/gl2.h>
#include <utils/logutils.h>
#include "program.h"

namespace VideoEffect {

    void Program::init(const char *vsSource, const char *fsSource) {
        mProgramID = glCreateProgram();

        if (mProgramID <= 0) {
            GLenum err = glGetError();
            VE_LOGE("Failed to create program. error id: %d", err);
            return;
        }

        GLuint vertextShadler = 0;
        GLuint fragmentShadler = 0;
        if (!compileShader(&vertextShadler, GL_VERTEX_SHADER, vsSource)) {
            VE_LOGE("Failed to compile vertex shader");
            return;
        }

        if (!compileShader(&fragmentShadler, GL_FRAGMENT_SHADER, fsSource)) {
            VE_LOGE("Failed to compile fragment shader");
            return;
        }

        glAttachShader(mProgramID, vertextShadler);
        glAttachShader(mProgramID, fragmentShadler);

        if (!linkProgram(mProgramID)) {
            VE_LOGE("Failed to link program: %d", mProgramID);

            glDeleteShader(vertextShadler);
            vertextShadler = 0;

            glDeleteShader(fragmentShadler);
            fragmentShadler = 0;

            glDeleteProgram(mProgramID);
            mProgramID = 0;

            return;
        }

        glDetachShader(mProgramID, vertextShadler);
        glDeleteShader(vertextShadler);

        glDetachShader(mProgramID, fragmentShadler);
        glDeleteShader(fragmentShadler);

    }

    void Program::use() {
        glUseProgram(mProgramID);
    }

    void Program::setVertexAttribPointer(const std::string &name, GLint size, GLenum type, GLboolean normalized, GLsizei stride, void *ptr) {
        GLint index = getHandle(name);
        if (index != -1) {
            glEnableVertexAttribArray(index);
            glVertexAttribPointer(index, size, type, normalized, stride, ptr);
        }
    }

    void Program::disableVertexAttrib(const std::string &name) {
        GLint index = getHandle(name);
        if (index != -1) {
            glDisableVertexAttribArray(index);
        }
    }

    void Program::setUniformTexture(const std::string &name, GLint x, GLuint textureID, GLenum target) {
        int location = getHandle(name);
        if (location != -1) {
            glUniform1i(location, x);
            glActiveTexture(GL_TEXTURE0 + x);
            glBindTexture(target, textureID);
        }
    }

    void Program::setUniformInt(const std::string &name, GLint value) {
        int location = getHandle(name);
        if (location != -1) {
            glUniform1i(location, value);
        }
    }

    void Program::setUniformFloat(const std::string &name, GLfloat value) {
        int location = getHandle(name);
        if (location != -1) {
            glUniform1f(location, value);
        }
    }

    GLint Program::getHandle(const std::string &name) {
        handle_map_type::const_iterator it = mShaderHandleMap.find(name);
        if (it != mShaderHandleMap.end()) {
            return it->second;
        }

        GLint handle = glGetAttribLocation(mProgramID, name.c_str());
        if (handle == -1) {
            handle = glGetUniformLocation(mProgramID, name.c_str());
        }

        if (handle == -1) {
            VE_LOGE("Location for %s is not found!", name.c_str());
        } else {
            mShaderHandleMap.insert(handle_map_type::value_type(name, handle));
        }

        return handle;
    }

    bool Program::compileShader(GLuint *shader, GLenum type, const char *source) {
        const GLchar *sources[] = {source};
        //创建一个着色器对象
        *shader = glCreateShader(type);
        //把这个着色器源码附加到着色器对象上
        glShaderSource(*shader, sizeof(sources) / sizeof(*sources), sources, NULL);
        //然后编译着色器
        glCompileShader(*shader);

        GLint logLength;
        //检测在调用glCompileShader后编译是否成功了
        glGetShaderiv(*shader, GL_INFO_LOG_LENGTH, &logLength);
        if (logLength > 0) {
            char *log = (char *) malloc(logLength);
            glGetShaderInfoLog(*shader, logLength, &logLength, log);
            VE_LOGE("Shader compile log: %s", log);
//            LOGD("PROGRAM", "Shader compile log: %s", log);
            free(log);
        }

        GLint status;
        //检测在调用glCompileShader后编译是否成功了
        glGetShaderiv(*shader, GL_COMPILE_STATUS, &status);
        if (status == 0) {
            glDeleteShader(*shader);
            *shader = 0;
            return false;
        }

        return true;
    }


    bool Program::linkProgram(GLuint programId) {

        glLinkProgram(programId);

        // Check the program
        GLint logLength;
        //检查链接是否成功
        glGetProgramiv(programId, GL_INFO_LOG_LENGTH, &logLength);
        if (logLength > 0) {
            char *log = (char *) malloc(logLength);
            glGetProgramInfoLog(programId, logLength, &logLength, log);
            VE_LOGE("Program link log: %s", log);
            free(log);
        }

        GLint status;
        glGetProgramiv(programId, GL_LINK_STATUS, &status);
        if (status == 0) {
            return false;
        }
        return true;
    }

}